# Copyright (c) 2018 Presto Labs Pte. Ltd.
# Author: leon

import datetime

from recordclass import recordclass

LongestNoFeed = recordclass('LongestNoFeed', ['begin', 'end', 'time_delta', 'last_timestamp'])

FeedCheckerInvalid = recordclass('FeedCheckerInvalid',
                                 [
                                     'feed_checker_invalid_count',
                                     'max_feed_checker_consecutive_invalid_count',
                                     'max_feed_checker_consecutive_invalid_period',
                                     'latest_feed_checker_consecutive_invalid_count',
                                     'latest_feed_checker_consecutive_invalid_begin'
                                 ])

BookStats = recordclass('BookStats',
                        [
                            'product',
                            'start_time',
                            'end_time',
                            'book_count',
                            'each_hour_count',
                            'last_bid0_price',
                            'last_ask0_price',
                            'crossed_book_count',
                            'mpc_count',
                            'bid0_qty_total',
                            'ask0_qty_total',
                            'min_side_qty_total',
                            'max_side_qty_total',
                            'bidask_spread_total',
                            'price_flip_count',
                            'avg_price_flip_interval',
                            'longest_no_feed',
                            'feed_checker_invalid'
                        ])


def gen_empty_BookStats():
  return BookStats(None,
                   0,
                   0,
                   0, [0] * 24,
                   0,
                   0,
                   0,
                   0,
                   0,
                   0,
                   0,
                   0,
                   0,
                   0,
                   0,
                   LongestNoFeed(0, 0, 0, 0),
                   FeedCheckerInvalid(0, 0, 0, 0, 0))


def calculate_longest_no_feed(book, book_stats):
  if book_stats.longest_no_feed.begin == 0:
    book_stats.longest_no_feed.begin = book.timestamp
    book_stats.longest_no_feed.end = book.timestamp
    book_stats.longest_no_feed.time_delta = 0
    book_stats.longest_no_feed.last_timestamp = book.timestamp
  else:
    if book.timestamp - book_stats.longest_no_feed.last_timestamp > \
        book_stats.longest_no_feed.time_delta:
      book_stats.longest_no_feed.begin = book_stats.longest_no_feed.last_timestamp
      book_stats.longest_no_feed.end = book.timestamp
      book_stats.longest_no_feed.time_delta = book_stats.longest_no_feed.end - \
          book_stats.longest_no_feed.begin
    book_stats.longest_no_feed.last_timestamp = book.timestamp


def calculate_feed_checker_invalid(book, book_stats, invalid):
  if invalid:
    book_stats.feed_checker_invalid.feed_checker_invalid_count += 1
    book_stats.feed_checker_invalid.latest_feed_checker_consecutive_invalid_count += 1
    if book_stats.feed_checker_invalid.latest_feed_checker_consecutive_invalid_count > \
        book_stats.feed_checker_invalid.max_feed_checker_consecutive_invalid_count:
      book_stats.feed_checker_invalid.max_feed_checker_consecutive_invalid_count = \
        book_stats.feed_checker_invalid.latest_feed_checker_consecutive_invalid_count

    if book_stats.feed_checker_invalid.latest_feed_checker_consecutive_invalid_begin == 0:
      book_stats.feed_checker_invalid.latest_feed_checker_consecutive_invalid_begin = \
          book.timestamp
    latest_feed_checker_consecutive_invalid_period = book.timestamp - \
        book_stats.feed_checker_invalid.latest_feed_checker_consecutive_invalid_begin
    if latest_feed_checker_consecutive_invalid_period > \
        book_stats.feed_checker_invalid.max_feed_checker_consecutive_invalid_period:
      book_stats.feed_checker_invalid.max_feed_checker_consecutive_invalid_period = \
          latest_feed_checker_consecutive_invalid_period
  else:
    book_stats.feed_checker_invalid.latest_feed_checker_consecutive_invalid_count = 0
    book_stats.feed_checker_invalid.latest_feed_checker_consecutive_invalid_begin = 0


class BookStatsCalculator(object):
  def __init__(self, book_stats):
    self._book_stats = book_stats
    self._prev_flip_time = None

  def set_product(self, product):
    self.book_stats.product = product

  @property
  def book_stats(self):
    return self._book_stats

  def update_by_book(self, book, feed_checker, product):
    book_stats = self.book_stats

    try:
      bid0_price = book.bid0().price
      ask0_price = book.ask0().price
    except (AttributeError, IndexError):
      return
    bid0_price = book.bid0().price
    ask0_price = book.ask0().price
    if book_stats.start_time == 0:
      book_stats.start_time = book.timestamp
      book_stats.last_bid0_price = bid0_price
      book_stats.last_ask0_price = ask0_price
    book_stats.end_time = book.timestamp
    book_stats.book_count += 1
    hour = datetime.datetime.fromtimestamp(book.timestamp / (10.**9)).hour
    book_stats.each_hour_count[hour] += 1

    if bid0_price >= ask0_price:
      book_stats.crossed_book_count += 1
    else:
      book_stats.bidask_spread_total += ask0_price - bid0_price
      if book.bid0().price >= book_stats.last_ask0_price or \
         book.ask0().price <= book_stats.last_bid0_price:
        if self._prev_flip_time is not None:
          avg_interval = book_stats.avg_price_flip_interval
          flip_cnt = book_stats.price_flip_count
          curr_interval = book.timestamp - self._prev_flip_time
          avg_interval = (avg_interval * (flip_cnt - 1) + curr_interval) / flip_cnt
          book_stats.avg_price_flip_interval = avg_interval
        self._prev_flip_time = book.timestamp
        book_stats.price_flip_count += 1
    if bid0_price != book_stats.last_bid0_price or \
        ask0_price != book_stats.last_ask0_price:
      book_stats.mpc_count += 1
    book_stats.last_bid0_price = bid0_price
    book_stats.last_ask0_price = ask0_price

    bid0_qty = book.bid0().qty
    ask0_qty = book.ask0().qty
    book_stats.bid0_qty_total += bid0_qty
    book_stats.ask0_qty_total += ask0_qty
    min_side_qty = min(bid0_qty, ask0_qty)
    max_side_qty = max(bid0_qty, ask0_qty)
    book_stats.min_side_qty_total += min_side_qty
    book_stats.max_side_qty_total += max_side_qty

    calculate_longest_no_feed(book, book_stats)

    state = feed_checker.check_per_product_state(product=product, timestamp=book.timestamp)
    calculate_feed_checker_invalid(book, book_stats, state.invalid)
    if state.invalid:
      feed_checker.restart(timestamp=book.timestamp)
